﻿using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Linq;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using Nsf.Samples.RetryActivitySample.Infrastructure.InternalInterfaces;
using Nsf.Samples.RetryActivitySample.WorkflowBase.Designer;
using Nsf.Samples.RetryActivitySample.WorkflowBase.Exceptions;

namespace Nsf.Samples.RetryActivitySample.WorkflowBase.Activities
{
    [TypeConverter(typeof(PropertySorter))]
	public partial class RetryableActivity: Activity
    {
        #region Constructors

        public RetryableActivity()
		{
			InitializeComponent();
        }

        public RetryableActivity(String name) : base(name)
        {

        }

        #endregion

        #region DependencyProperties

        public static DependencyProperty UseAutomaticRetryProperty =
            DependencyProperty.Register("UseAutomaticRetry",
                                        typeof(Boolean),
                                        typeof(RetryableActivity),
                                        new PropertyMetadata(DependencyPropertyOptions.Default)
                );
        
        public static DependencyProperty RetryCountProperty =
	        DependencyProperty.Register("RetryCount",
	                                    typeof (Int32),
	                                    typeof (RetryableActivity),
	                                    new PropertyMetadata(DependencyPropertyOptions.Default)
	            );

        public static DependencyProperty RetryIntervalProperty =
            DependencyProperty.Register("RetryInterval",
                                        typeof(Int32),
                                        typeof(RetryableActivity),
                                        new PropertyMetadata(DependencyPropertyOptions.Default)
                );


        public static DependencyProperty UseManualRetryProperty =
            DependencyProperty.Register("UseManualRetry",
                                        typeof(Boolean),
                                        typeof(RetryableActivity),
                                        new PropertyMetadata(DependencyPropertyOptions.Default)
                );


        #endregion

        #region InternalProperties

        [Browsable(false)]
        public Int32 CurrentRetryCount { get; set; }

        #endregion

        #region Properties

        [PropertyOrder(10)]
        [Category("Автоматический перезапуск")]
        [DisplayName("Использовать автоматический перезапуск")]
        [Description("Если это свойство равно True, то будет осуществляться автоматический перезапуск")]
        public Boolean UseAutomaticRetry
        {
            get { return (Boolean) GetValue(UseAutomaticRetryProperty); }
            set { SetValue(UseAutomaticRetryProperty, value); }
        }

        [PropertyOrder(20)]
        [Category("Автоматический перезапуск")]
        [DisplayName("Количество попыток перезапуска")]
        [Description("Количество попыток перезапуска, до окончательной остановки")]
	    public Int32 RetryCount
	    {
            get { return (Int32) GetValue(RetryCountProperty); }
            set { SetValue(RetryCountProperty, value); }
	    }

        [PropertyOrder(30)]
        [Category("Автоматический перезапуск")]
        [DisplayName("Интервал перезапуска")]
        [Description("Интервал между попытками перезапуска. Задается в секундах")]
        public Int32 RetryInterval
        {
            get { return (Int32) GetValue(RetryIntervalProperty);    }
            set { SetValue(RetryIntervalProperty, value); }
        }

        [PropertyOrder(40)]
        [Category("Ручной перезапуск")]
        [DisplayName("Использовать ручной перезапуск")]
        [Description("Если это свойство равно True, то после окончания автоматического перезапуска будет возможность перезапустить вручную")]
        public Boolean UseManualRetry
        {
            get { return (Boolean) GetValue(UseManualRetryProperty); }
            set { SetValue(UseManualRetryProperty, value); }
        }


        #endregion

        #region Internal Methods

        private void CleanupSubscriptions(ActivityExecutionContext activityExecutionContext, Guid subscriptionId)
        {
            var workflowQueueService = activityExecutionContext.GetService<WorkflowQueuingService>();
            if (workflowQueueService != null)
                workflowQueueService.DeleteWorkflowQueue(subscriptionId);

            if (!NeedAutomaticRetry())
                return;

            var timers = GetTimerSubscriptionCollection();
            if (timers != null)
                timers.Remove(subscriptionId);
        }

        private Boolean NeedAutomaticRetry()
        {
            return UseAutomaticRetry && (RetryCount <= 0 || (RetryCount > CurrentRetryCount));
        }

        private TimerEventSubscriptionCollection GetTimerSubscriptionCollection()
        {
            Activity timersContainer = this;
            while (timersContainer.Parent != null)
            {
                timersContainer = timersContainer.Parent;
            }
            var timers = (TimerEventSubscriptionCollection)timersContainer.GetValue(TimerEventSubscriptionCollection.TimerCollectionProperty);
            return timers;
        }
        
        private Boolean CreateWorkflowQueue(IComparable queueName, ActivityExecutionContext executionContext)
        {
            var workflowQueueService = executionContext.GetService<WorkflowQueuingService>();
            if (workflowQueueService == null)
                return false;
            var queue = workflowQueueService.CreateWorkflowQueue(queueName, false);
            queue.QueueItemAvailable += OnQueueItemAvailable;
            return true;
        }

        private Boolean CreateTimerSubscription(Guid timerId)
        {
            var expiresAt = DateTime.UtcNow.AddSeconds(RetryInterval);
            var timerSubscription = new TimerEventSubscription(timerId, WorkflowInstanceId, expiresAt);
            var timers = GetTimerSubscriptionCollection();
            if (timers == null)
                return false;
            timers.Add(timerSubscription);
            return true;
        }


        private Boolean ProcessException(Exception ex, ActivityExecutionContext executionContext)
        {
            var logger = executionContext.GetService<ILogger>();
            if (logger != null)
                logger.Warning(ex.Message, ex);

            var queueName = Guid.NewGuid();
            if (!CreateWorkflowQueue(queueName, executionContext))
            {
                if (logger != null)
                    logger.Error("Не удалось создать очередь для рабочего потока");
                return false;
            }
            if (NeedAutomaticRetry())
            {
                if (CreateTimerSubscription(queueName))
                    return true;

                if (logger != null)
                    logger.Error("Не удалось создайть таймер для рабочего потока");
                CurrentRetryCount = RetryCount;
            }
            if (!UseManualRetry)
                return false;

            TrackException(ex, queueName);
            return true;
        }

        private void TrackException(Exception ex, Guid queueName)
        {
            var data = new RetryableActivityErrorInfo()
            {
                ErrorMessage = ex.Message,
                QueueName = queueName,
                ActivityName = Name,
                ActivityDescription = Description,
                WorkflowInstanceId = WorkflowInstanceId
            };
            TrackData("QueueCreated", data);
        }

        private void OnQueueItemAvailable(object sender, QueueEventArgs e)
        {
            var executionContext = (ActivityExecutionContext)sender;

            var logger = executionContext.GetService<ILogger>();
            CleanupSubscriptions(executionContext, (Guid) e.QueueName);

            if (NeedAutomaticRetry())
            {
                if (logger != null)
                {
                    var messageTemplate = "Рабочий поток '{0}'. Повторное выполнение действия '{1}', осталось попыток: {2}";
                    var message = String.Format(messageTemplate, WorkflowInstanceId, Name, RetryCount - CurrentRetryCount);
                    logger.Info(message);
                }
                CurrentRetryCount++;
            }
            else
                TrackData("QueueClosed", (Guid)e.QueueName);

            try
            {
                var status = OnExecute(executionContext);
                if (status == ActivityExecutionStatus.Closed)
                    executionContext.CloseActivity();
            }
            catch (FatalException ex)
            {
                if (logger != null)
                    logger.Error(ex.Message, ex);
                throw;
            }
            catch (Exception ex)
            {
                if (!ProcessException(ex, executionContext))
                    throw;
            }
        }

        #endregion

        #region Executing

        public virtual ActivityExecutionStatus OnExecute(ActivityExecutionContext executionContext)
        {
            return ActivityExecutionStatus.Closed;
        }

        protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
        {
            var logger = executionContext.GetService<ILogger>();
            try
            {
                return OnExecute(executionContext);
            }
            catch (FatalException ex)
            {
                if (logger != null)
                    logger.Error(ex.Message, ex);
                throw;
            }
            catch (Exception ex)
            {
                if (!ProcessException(ex, executionContext))
                    throw;
                return ActivityExecutionStatus.Executing;
            }
        }

        #endregion

    }
}
